/*
 * Create new 2.xBSD filesystem.
 *
 * Copyright (C) 2006-2014 Serge Vakulenko, <serge@vak.ru>
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and this
 * permission notice and warranty disclaimer appear in supporting
 * documentation, and that the name of the author not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * The author disclaim all warranties with regard to this
 * software, including all implied warranties of merchantability
 * and fitness.  In no event shall the author be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether
 * in an action of contract, negligence or other tortious action,
 * arising out of or in connection with the use or performance of
 * this software.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include "bsdfs.h"

extern int verbose;

int inode_build_list (fs_t *fs)
{
    fs_inode_t inode;
    unsigned int inum, total_inodes;

    total_inodes = (fs->isize - 1) * BSDFS_INODES_PER_BLOCK;
    for (inum = 1; inum <= total_inodes; inum++) {
        if (! fs_inode_get (fs, &inode, inum))
            return 0;
        if (inode.mode == 0) {
            fs->inode [fs->ninode++] = inum;
            if (fs->ninode >= NICINOD)
                break;
        }
    }
    return 1;
}

static int create_inode1 (fs_t *fs)
{
    fs_inode_t inode;

    memset (&inode, 0, sizeof(inode));
    inode.mode = INODE_MODE_FREG;
    inode.fs = fs;
    inode.number = 1;
    if (! fs_inode_save (&inode, 1))
        return 0;
    fs->tinode--;
    return 1;
}

static int create_root_directory (fs_t *fs)
{
    fs_inode_t inode;
    unsigned char buf [BSDFS_BSIZE];
    unsigned int bno;

    memset (&inode, 0, sizeof(inode));
    inode.mode = INODE_MODE_FDIR | 0777;
    inode.fs = fs;
    inode.number = BSDFS_ROOT_INODE;
    inode.size = BSDFS_BSIZE;
    inode.flags = 0;

    time (&inode.ctime);
    time (&inode.atime);
    time (&inode.mtime);

    /* directory - put in extra links */
    memset (buf, 0, sizeof(buf));
    buf[0] = inode.number;
    buf[1] = inode.number >> 8;
    buf[2] = inode.number >> 16;
    buf[3] = inode.number >> 24;
    buf[4] = 12;
    buf[5] = 12 >> 8;
    buf[6] = 1;
    buf[7] = 1 >> 8;
    buf[8] = '.';
    buf[9] = 0;
    buf[10] = 0;
    buf[11] = 0;

    buf[12+0] = BSDFS_ROOT_INODE;
    buf[12+1] = BSDFS_ROOT_INODE >> 8;
    buf[12+2] = BSDFS_ROOT_INODE >> 16;
    buf[12+3] = BSDFS_ROOT_INODE >> 24;
    buf[12+4] = 12;
    buf[12+5] = 12 >> 8;
    buf[12+6] = 2;
    buf[12+7] = 2 >> 8;
    buf[12+8] = '.';
    buf[12+9] = '.';
    buf[12+10] = 0;
    buf[12+11] = 0;

    buf[24+0] = BSDFS_LOSTFOUND_INODE;
    buf[24+1] = BSDFS_LOSTFOUND_INODE >> 8;
    buf[24+2] = BSDFS_LOSTFOUND_INODE >> 16;
    buf[24+3] = BSDFS_LOSTFOUND_INODE >> 24;
    buf[24+4] = (unsigned char) (BSDFS_BSIZE - 12 - 12);
    buf[24+5] = (BSDFS_BSIZE - 12 - 12) >> 8;
    buf[24+6] = 10;
    buf[24+7] = 10 >> 8;
    memcpy (&buf[24+8], "lost+found\0\0", 12);

    if (fs->swapsz != 0) {
        buf[24+4] = 20;
        buf[24+5] = 20 >> 8;
        buf[44+0] = BSDFS_SWAP_INODE;
        buf[44+1] = BSDFS_SWAP_INODE >> 8;
        buf[44+2] = BSDFS_SWAP_INODE >> 16;
        buf[44+3] = BSDFS_SWAP_INODE >> 24;
        buf[44+4] = (unsigned char) (BSDFS_BSIZE - 12 - 12 - 20);
        buf[44+5] = (BSDFS_BSIZE - 12 - 12 - 20) >> 8;
        buf[44+6] = 4;
        buf[44+7] = 4 >> 8;
        memcpy (&buf[44+8], "swap\0\0\0\0", 8);
    }
    inode.nlink = 3;

    if (! fs_block_alloc (fs, &bno))
        return 0;
    if (! fs_write_block (fs, bno, buf))
        return 0;
    inode.addr[0] = bno;

    if (! fs_inode_save (&inode, 1))
        return 0;
    fs->tinode--;
    return 1;
}

static int create_lost_found_directory (fs_t *fs)
{
    fs_inode_t inode;
    unsigned char buf [BSDFS_BSIZE];
    unsigned int bno;

    memset (&inode, 0, sizeof(inode));
    inode.mode = INODE_MODE_FDIR | 0777;
    inode.fs = fs;
    inode.number = BSDFS_LOSTFOUND_INODE;
    inode.size = BSDFS_BSIZE;
    inode.flags = 0;

    time (&inode.ctime);
    time (&inode.atime);
    time (&inode.mtime);

    /* directory - put in extra links */
    memset (buf, 0, sizeof(buf));
    buf[0] = inode.number;
    buf[1] = inode.number >> 8;
    buf[2] = inode.number >> 16;
    buf[3] = inode.number >> 24;
    buf[4] = 12;
    buf[5] = 12 >> 8;
    buf[6] = 1;
    buf[7] = 1 >> 8;
    buf[8] = '.';
    buf[9] = 0;
    buf[10] = 0;
    buf[11] = 0;

    buf[12+0] = BSDFS_ROOT_INODE;
    buf[12+1] = BSDFS_ROOT_INODE >> 8;
    buf[12+2] = BSDFS_ROOT_INODE >> 16;
    buf[12+3] = BSDFS_ROOT_INODE >> 24;
    buf[12+4] = (unsigned char) (BSDFS_BSIZE - 12);
    buf[12+5] = (BSDFS_BSIZE - 12) >> 8;
    buf[12+6] = 2;
    buf[12+7] = 2 >> 8;
    buf[12+8] = '.';
    buf[12+9] = '.';
    buf[12+10] = 0;
    buf[12+11] = 0;

    inode.nlink = 2;

    if (! fs_block_alloc (fs, &bno))
        return 0;
    if (! fs_write_block (fs, bno, buf))
        return 0;
    inode.addr[0] = bno;

    if (! fs_inode_save (&inode, 1))
        return 0;
    fs->tinode--;
    return 1;
}

static void map_block_swap (fs_inode_t *inode, unsigned lbn)
{
    unsigned block [BSDFS_BSIZE / 4];
    unsigned int bn, indir, newb, shift, i, j;

    /*
     * Blocks 0..NADDR-3 are direct blocks.
     */
    if (lbn < NADDR-3) {
        /* small file algorithm */
        inode->addr[lbn] = inode->fs->isize + lbn;
        return;
    }

    /*
     * Addresses NADDR-3, NADDR-2, and NADDR-1
     * have single, double, triple indirect blocks.
     * The first step is to determine
     * how many levels of indirection.
     */
    shift = 0;
    i = 1;
    bn = lbn - (NADDR-3);
    for (j=3; ; j--) {
        if (j == 0) {
            fprintf (stderr, "swap: too large size\n");
            exit (-1);
        }
        shift += NSHIFT;
        i <<= NSHIFT;
        if (bn < i)
            break;
        bn -= i;
    }

    /*
     * Fetch the first indirect block.
     */
    indir = inode->addr [NADDR-j];
    if (indir == 0) {
        if (! fs_block_alloc (inode->fs, &indir)) {
alloc_error:
            fprintf (stderr, "swap: cannot allocate indirect block\n");
            exit (-1);
        }
        if (verbose)
            printf ("swap: allocate indirect block %d (j=%d)\n", indir, j);
        memset (block, 0, BSDFS_BSIZE);
        if (! fs_write_block (inode->fs, indir, (unsigned char*) block)) {
write_error:
            fprintf (stderr, "swap: cannot write indirect block %d\n", indir);
            exit (-1);
        }
        inode->addr [NADDR-j] = indir;
    }

    /*
     * Fetch through the indirect blocks
     */
    for (; ; j++) {
        if (! fs_read_block (inode->fs, indir, (unsigned char*) block)) {
            fprintf (stderr, "swap: cannot read indirect block %d\n", indir);
            exit (-1);
        }
        shift -= NSHIFT;
        i = (bn >> shift) & NMASK;
        if (j == 3) {
            block[i] = inode->fs->isize + lbn;
            if (! fs_write_block (inode->fs, indir, (unsigned char*) block))
                goto write_error;
            return;
        }
        if (block[i] != 0) {
               indir = block [i];
               continue;
        }
        /* Allocate new indirect block. */
        if (! fs_block_alloc (inode->fs, &newb))
            goto alloc_error;
        if (verbose)
            printf ("swap: allocate new block %d (j=%d)\n", newb, j);
        block[i] = newb;
        if (! fs_write_block (inode->fs, indir, (unsigned char*) block))
            goto write_error;
        memset (block, 0, BSDFS_BSIZE);
        if (! fs_write_block (inode->fs, newb, (unsigned char*) block)) {
            fprintf (stderr, "swap: cannot write block %d\n", newb);
            exit (-1);
        }
        indir = newb;
    }
}

static int create_swap_file (fs_t *fs)
{
    fs_inode_t inode;
    unsigned lbn;

    memset (&inode, 0, sizeof(inode));
    inode.mode = INODE_MODE_FREG | 0400;
    inode.fs = fs;
    inode.number = BSDFS_SWAP_INODE;
    inode.size = fs->swapsz * BSDFS_BSIZE;
    inode.flags = /*SYS_IMMUTABLE |*/ USER_IMMUTABLE | USER_NODUMP;
    inode.nlink = 1;
    inode.dirty = 1;

    time (&inode.ctime);
    time (&inode.atime);
    time (&inode.mtime);

    for (lbn=0; lbn<fs->swapsz; lbn++)
        map_block_swap (&inode, lbn);

    if (! fs_inode_save (&inode, 0)) {
        fprintf (stderr, "swap: cannot save file inode\n");
        return 0;
    }
    return 1;
}

int fs_create (fs_t *fs, const char *filename, int kbytes,
    unsigned swap_kbytes)
{
    int n;
    unsigned char buf [BSDFS_BSIZE];
    off_t bytes, offset;

    memset (fs, 0, sizeof (*fs));
    fs->filename = filename;
    fs->seek = 0;

    if (kbytes < 0) {
        fs->fd = open (fs->filename, O_RDWR);
        if (fs->fd < 0)
            return 0;

        /* Get size and offset from partition table. */
        if (! fs_set_partition (fs, -kbytes))
            return 0;
        kbytes = fs->part_nsectors / 2;
    } else {
        fs->fd = open (fs->filename, O_CREAT | O_RDWR, 0666);
        if (fs->fd < 0)
            return 0;
    }
    fs->writable = 1;

    /* get total disk size
     * and inode block size */
    bytes = (off_t) kbytes * 1024ULL;
    fs->fsize = bytes / BSDFS_BSIZE;
    fs->isize = 1 + (fs->fsize / 16 + BSDFS_INODES_PER_BLOCK - 1) /
        BSDFS_INODES_PER_BLOCK;
    if (fs->isize < 2)
        return 0;

    /* make sure the file is of proper size */
    offset = lseek (fs->fd, fs->part_offset + bytes-1, SEEK_SET);
    if (offset != fs->part_offset + bytes-1)
        return 0;
    if (write (fs->fd, "", 1) != 1) {
        perror ("write");
        return 0;
    }
    lseek (fs->fd, fs->part_offset, SEEK_SET);

    /* build a list of free blocks */
    fs->swapsz = swap_kbytes * 1024 / BSDFS_BSIZE;
    fs_block_free (fs, 0);
    for (n = fs->fsize - 1; n >= fs->isize + fs->swapsz; n--)
        if (! fs_block_free (fs, n))
            return 0;

    /* initialize inodes */
    memset (buf, 0, BSDFS_BSIZE);
    if (! fs_seek (fs, BSDFS_BSIZE))
        return 0;
    for (n=1; n < fs->isize; n++) {
        if (! fs_write (fs, buf, BSDFS_BSIZE))
            return 0;
        fs->tinode += BSDFS_INODES_PER_BLOCK;
    }

    /* legacy empty inode 1 */
    if (! create_inode1 (fs))
        return 0;

    /* lost+found directory */
    if (! create_lost_found_directory (fs))
        return 0;

    /* root directory */
    if (! create_root_directory (fs))
        return 0;

    /* swap file */
    if (fs->swapsz != 0 && ! create_swap_file (fs))
        return 0;

    /* build a list of free inodes */
    if (! inode_build_list (fs))
        return 0;

    /* write out super block */
    return fs_sync (fs, 1);
}
